feat: Use @typegpu/gl as a fallback when @typegpu/three is made to generate GLSL - #2794
Conversation
|
pkg.pr.new packages benchmark commit |
Resolution Time Benchmark---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Random Branching (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.67, 1.27, 2.76, 4.59, 4.70, 8.73, 15.42, 16.69]
line [0.62, 1.19, 2.74, 4.15, 4.86, 8.59, 14.80, 17.22]
line [0.63, 1.23, 2.55, 4.32, 4.84, 7.83, 15.03, 17.33]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Linear Recursion (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.22, 0.35, 0.46, 0.59, 0.77, 0.82, 0.96, 1.07]
line [0.21, 0.31, 0.44, 0.53, 0.71, 0.79, 0.91, 1.01]
line [0.25, 0.42, 0.52, 0.63, 0.82, 0.88, 1.03, 1.06]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Full Tree (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.60, 1.83, 3.27, 5.07, 8.44, 17.38, 36.91, 75.63]
line [0.58, 1.45, 2.67, 4.43, 8.16, 17.28, 35.60, 76.05]
line [0.61, 1.47, 3.33, 4.45, 8.45, 18.22, 36.67, 76.91]
|
Bundle size comparison (
|
| 🟢 Decreased | ➖ Unchanged | 🔴 Increased | ❔ Unknown |
|---|---|---|---|
| 0 | 324 | 0 | 0 |
import { ... } in PR vs import * as ... in PR (is the library tree-Shakeable?):
| Test | tsdown |
|---|---|
| tgpu_init.ts | 270.69 kB ( |
| tgpu_initFromDevice.ts | 270.15 kB ( |
| tgpu_resolve.ts | 170.93 kB ( |
| tgpu_resolveWithContext.ts | 170.87 kB ( |
| tgpu_bindGroupLayout.ts | 73.93 kB ( |
| tgpu_mutableAccessor.ts | 68.66 kB ( |
| tgpu_accessor.ts | 68.66 kB ( |
| tgpu_privateVar.ts | 67.35 kB ( |
| tgpu_workgroupVar.ts | 67.35 kB ( |
| tgpu_const.ts | 66.77 kB ( |
| tgpu_lazy.ts | 66.56 kB ( |
| tgpu_fragmentFn.ts | 38.92 kB ( |
| tgpu_fn.ts | 38.87 kB ( |
| tgpu_vertexFn.ts | 38.74 kB ( |
| tgpu_computeFn.ts | 38.44 kB ( |
| tgpu_vertexLayout.ts | 27.57 kB ( |
| tgpu_comptime.ts | 15.18 kB ( |
| tgpu_unroll.ts | 1.75 kB ( |
| tgpu_slot.ts | 1.70 kB ( |
If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.
There was a problem hiding this comment.
Pull request overview
This PR updates @typegpu/three to support Three.js’s WebGL backend path by switching TypeGPU shader generation to GLSL via @typegpu/gl when WebGL is detected.
Changes:
- Add
@typegpu/glas a peer dependency and wire it into the workspace lockfile. - Detect WebGL backend in the node builder and apply
glOptions({ shaderStage: 'none' })totgpu.resolve(...)calls. - Adjust function-start detection to handle GLSL-style function declarations.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| pnpm-lock.yaml | Adds @typegpu/gl to the workspace install graph for packages/typegpu-three. |
| packages/typegpu-three/src/typegpu-node.ts | Adds WebGL detection + GLSL generation options via glOptions, and updates function-start detection. |
| packages/typegpu-three/package.json | Declares @typegpu/gl as a peer dependency for @typegpu/three. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Caution
The WebGL fallback path now generates GLSL, but forceExplicitVoidReturn is WGSL-only: it appends -> void after the first ), corrupting every GLSL function signature. This prevents the fallback from working. See the inline comment on packages/typegpu-three/src/typegpu-node.ts:193.
Reviewed changes
- Declared
@typegpu/glas a peer dependency and wiredglOptions({ shaderStage: 'none' })into the threetgpu.resolvecalls when the Three.js builder targets WebGL. - Added
isWebGL()backend detection andfindFunctionStart()to handle both WGSL (fn name) and GLSL (type name() function declaration syntax. - Refactored the dependency-assignment resolution in
generate()into a localcodevariable. - Updated
pnpm-lock.yamlto link the new peer dependency.
ℹ️ Docs will need updating once WebGL works
The @typegpu/three docs currently include a caution block stating that the WebGL fallback does not work. Once this feature branch makes it work, that block and the install instructions should be updated.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
da61438 to
a010b4f
Compare
|
Follow-up to the review above with line-specific notes:
|
a010b4f to
2cc2d21
Compare
There was a problem hiding this comment.
Important
The @typegpu/gl delta improves GLSL emission, but the WebGL fallback path is still blocked because forceExplicitVoidReturn corrupts GLSL signatures.
Reviewed changes
Reviewed the delta that removed the WORKAROUND_wgslToGlslFixups post-processor from @typegpu/gl and replaced it with native GLSL generation.
- Removed
WORKAROUND_wgslToGlslFixupsfrompackages/typegpu-gl/src/tgpuRootWebGL.tsand its export inpackages/typegpu-gl/src/index.ts. - Added native array support in
packages/typegpu-gl/src/glslGenerator.ts:resolveArraySizeSuffix, array-awaredeclareGlobalConst,declareGlobalVar,typeAnnotation,typeInstantiation, and_emitVarDeclnow emit GLSLtype name[N]declarations andtype[N](...)constructors. - Added a
numericLiteraloverride that emits GLSL literals without WGSLf/hsuffixes, keepsuforu32, and chooses decimal or exponential form for floats. - Updated
@typegpu/gltest snapshots; all 35@typegpu/gltests pass.
⚠️ WebGL fallback remains broken for GLSL signatures
packages/typegpu-three/src/typegpu-node.ts:193 still applies forceExplicitVoidReturn(fnCode) unconditionally. The helper inserts -> void after the first ), which corrupts GLSL function signatures such as float foo() into float foo()-> void. This prevents the WebGL fallback from working even though @typegpu/gl now emits valid GLSL.
Technical details
# forceExplicitVoidReturn corrupts GLSL signatures
## Affected sites
- packages/typegpu-three/src/typegpu-node.ts:193 — `forceExplicitVoidReturn(fnCode)` is applied to GLSL code
## Required outcome
- GLSL function code must not be passed through the WGSL-specific `forceExplicitVoidReturn` helper.
## Suggested approach
Skip `forceExplicitVoidReturn` when `webgl` is true, or detect whether the resolved code already has a return type in a language-agnostic way.Kimi K2 (free via Pullfrog for OSS) | 𝕏
2cc2d21 to
7b889ba
Compare
7b889ba to
961d532
Compare
961d532 to
6b3dea9
Compare
6b3dea9 to
24d8fe3
Compare
24d8fe3 to
75504e6
Compare
There was a problem hiding this comment.
Caution
The upstream @typegpu/gl fixes in this rebase are correct, but the PR's own @typegpu/three integration still applies a WGSL-only helper to GLSL code. The WebGL fallback therefore remains broken.
Reviewed changes
Reviewed the delta from the previous pullfrog review head (6b3dea9) to the current head (75504e6). The @typegpu/three changes are byte-for-byte identical to the previous iteration; the new commits come from the upstream feat/make-triangle-work-with-gl-fallback feature branch.
- Eagerly reserved
gl_Positionandsampleinpackages/typegpu-gl/src/glslGenerator.tson the firstfunctionDefinitioncall, so theshaderStage: 'none'path used by@typegpu/threecannot accidentally use these GLSL keywords as identifiers. - Updated
@typegpu/glinline snapshots to match improved numeric-literal and zero-constructor emission (1i→1,0i→0,1.5f→1.5,vec3()→vec3(0)). - The
@typegpu/threefiles are unchanged from the previous iteration.
🚨 WebGL fallback still blocked by forceExplicitVoidReturn
packages/typegpu-three/src/typegpu-node.ts:193 still passes the resolved function code through forceExplicitVoidReturn. That helper assumes WGSL syntax: it looks for -> to detect an explicit return type and otherwise appends -> void after the first ). GLSL function declarations do not contain ->, so a signature such as float foo() is corrupted into float foo()-> void, which is invalid GLSL. This was raised in the previous pullfrog review and has not been addressed in this rebase.
Technical details
# forceExplicitVoidReturn corrupts GLSL signatures
## Affected sites
- packages/typegpu-three/src/typegpu-node.ts:193 — `forceExplicitVoidReturn(fnCode)` is applied to GLSL code
## Required outcome
- GLSL function code must not be passed through the WGSL-specific `forceExplicitVoidReturn` helper.
## Suggested approach
Skip `forceExplicitVoidReturn` when `webgl` is true, or detect whether the resolved code already has a return type in a language-agnostic way.Kimi K2 (free via Pullfrog for OSS) | 𝕏
b4129ca to
fb2b7ae
Compare
There was a problem hiding this comment.
✅ No new issues found.
The prior
forceExplicitVoidReturnconcern is fixed, and the new GLSL implicit-pointer support is well covered by tests.
Reviewed changes
Reviewed the delta since the prior pullfrog review (75504e6). The latest commits add native GLSL handling for implicit const pointers and fix the WGSL-only return-type helper that was blocking the WebGL fallback.
- Fixed
forceExplicitVoidReturncorrupting GLSL signatures.packages/typegpu-three/src/typegpu-node.tsnow skips the WGSL-specific helper when the Three.js builder targets WebGL, so GLSL functions no longer get-> voidappended. - Refactored implicit-pointer
constemission.packages/typegpu/src/tgsl/wgslGenerator.tsextracted the alias logic into a protected_aliasConstStatementmethod so language-specific generators can override it. - Added GLSL implicit-pointer support.
packages/typegpu-gl/src/glslGenerator.tsoverrides_aliasConstStatement: immutable memory is copied, mutable memory is aliased by inlining the original expression, and runtime index expressions are hoisted intoletvariables to guarantee single evaluation. - Added test coverage.
packages/typegpu-gl/tests/implicitPointer.test.tscovers immutable copies, mutable aliases, nested member/index accesses, aliases of aliases, and local-variable aliases.
All @typegpu/gl and packages/typegpu tests pass.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
0bf429b to
5fa70c0
Compare
5fa70c0 to
96e6104
Compare
There was a problem hiding this comment.
Important
A new test in this delta fails: WebGL storage arrays > lowers array reads through a typed TSL helper and writes to the current element. The generated compute code is direct GLSL subscripting (void item() { vec3 value = storageValue[2]; storageValue[2] = value; }), which never contains the asserted typegpuReadStorage marker. That identifier does not exist anywhere in the codebase — it's referenced only by the test itself — so the assertion cannot pass as written. This blocks the merge until either the typed-helper lowering (or whatever the test is meant to pin) is implemented, or the expectation is corrected to match actual codegen.
Reviewed changes
Reviewed the delta since the prior pullfrog review (c4ac0890):
- Guarded
isWebGLagainst renderer-less builders.packages/typegpu-three/src/typegpu-node.ts:128now readsbuilder.renderer?.backendand returnsfalsewhen there is no renderer, fixing the open unguarded-deref concern. The newuses the active WebGL builder to infer nested toTSL return typestest pins the fix (passes with zeroconsole.warncalls). - Added a WebGL storage-array test that builds a fake storage node through setup/analyze/generate and asserts GLSL codegen — this test currently fails.
- Added
SetupStageData/getSetupStageDataplumbing to the builder data so WebGL builders can be exercised through thesetupbuild stage.
⚠️ New WebGL storage-array test fails
packages/typegpu-three/tests/typegpu-node.test.ts:169 asserts the generated compute code contains typegpuReadStorage, but the generator emits plain array indexing instead. Since typegpuReadStorage is defined nowhere in the repo, this test is red on a clean run (verified locally: 4/5 pass, this one fails). Either the storage-array lowering through the typed helper is intended-but-not-implemented (then it needs implementing), or the assertion is stale and should match the real storageValue[2] codegen.
Technical details
# New WebGL storage-array test fails
## Affected sites
- packages/typegpu-three/tests/typegpu-node.test.ts:169 — expects `typegpuReadStorage` in `builder.getCodes('compute')`
- packages/typegpu-three/tests/typegpu-node.test.ts:153-171 — the failing test itself
## Required outcome
- The test must pass on a clean run. Either implement the typed-helper lowering it describes (or confirm it isn't needed and drop the marker assertion), or fix the expectation to the actual generated code.
## Suggested approach
- Run `pnpm vitest run --project=!browser packages/typegpu-three/tests/typegpu-node.test.ts` to reproduce the failure, then decide whether the feature or the assertion is in error.ℹ️ Nitpicks
packages/typegpu-three/src/typegpu-node.ts:84-91—getSetupStageData/SetupStageData/setupStageDataMapare added but never called anywhere (onlygetGenerateStageData/getAnalyzeStageDatahave call sites). If thesetupbuild stage doesn't need its own stage data yet, this is dead code added by the delta.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
96e6104 to
f33e6db
Compare
f33e6db to
68c6f79
Compare
68c6f79 to
f1a4dba
Compare
f1a4dba to
7a52920
Compare
7a52920 to
0eb9c4f
Compare
4dd6e40 to
a048b8c
Compare
There was a problem hiding this comment.
Important
Deleting both WebGL storage arrays tests resolved the red test but also removed the only coverage of the WebGL/GLSL fallback — the feature this PR exists to deliver. Only the typegpuReadStorage assertion was wrong; the passing sibling test that pinned the isWebGL/glOptions wiring and the fromTSL warning regression should have been kept or rewritten, not deleted.
Reviewed changes
Reviewed the delta since the prior pullfrog review (0eb9c4fc):
- Deleted both
WebGL storage arraystests. The redtypegpuReadStoragetest (the prior merge blocker) and the passinguses the active WebGL builder to infer nested toTSL return typestest were removed together. The suite is green again (verified locally:typegpu-node.test.ts3/3,test:typespasses), but the throwaway scaffolding they exercised —webglBuilderFor,WebGLStorageArrayNode,THREEWebGLBackendMock, plus theGLSLNodeBuilderand now-unusedviimports — was left behind. Theunstable_minify: falselines intypegpu-node.tsare base-branch content, not part of this delta.
⚠️ WebGL fallback path now has zero automated test coverage
Only the red test needed to be removed — its typegpuReadStorage assertion was genuinely wrong, since the generator emits direct GLSL subscripting (storageValue[2]). The passing sibling (uses the active WebGL builder to infer nested toTSL return types) was the only test that drove a TgpuFnNode through a real GLSLNodeBuilder, asserting the isWebGL() + glOptions() + GLSL emission wiring works and that the fromTSL type-inference warning regression stays gone. With it deleted, nothing verifies that a TgpuFnNode actually resolves to GLSL when the builder targets the WebGL backend — the exact feature this PR adds. The remaining three tests only ever exercise the WGSL backend mock.
Technical details
# WebGL fallback path is untested after test deletion
## Affected sites
- packages/typegpu-three/tests/typegpu-node.test.ts — the deleted `uses the active WebGL builder to infer nested toTSL return types` test (removed in this delta)
## Required outcome
- The `isWebGL` detection, `glOptions()` application, and GLSL `fnDeclaration` emission in `typegpu-node.ts` must have at least one automated assertion, since the PR's whole purpose is making the WebGL/GLSL fallback work.
## Suggested approach
- Keep or restore the passing WebGL-builder test (or an equivalent that builds a `TgpuFnNode` with a WebGL backend and asserts `isWebGL`/`glOptions` behavior), and additionally remove the now-unused test scaffolding.
## Open questions for the human
- The red test also carried a `typegpuReadStorage` intent note ("lowers array reads through a typed TSL helper"). Is the typed-helper storage lowering intended-but-not-implemented, or is direct GLSL subscripting the correct codegen? If the former, that gap should be tracked separately rather than silently dropped with the test.ℹ️ Nitpicks
packages/typegpu-three/tests/typegpu-node.test.ts:61-81—webglBuilderFor,WebGLStorageArrayNode, andTHREEWebGLBackendMockare now unreferenced after both WebGL tests were deleted; theimport { ... vi }at line 6 is also unused. Leftover scaffolding from the removed tests.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
d51b0a3 to
dd642aa
Compare

No description provided.